/* See COPYRIGHT for copyright information. */

#include <mmu.h>
#include <error.h>
#include <env.h>
#include <pmap.h>
#include <printf.h>

struct Env *envs = NULL;                // All environments
struct Env *curenv = NULL;              // the current env

static struct Env_list env_free_list;   // Free list

extern Pde * boot_pgdir;
//static int KERNEL_SP;
extern char * KERNEL_SP;
//
// Calculates the envid for env e.  
//


u_int mkenvid(struct Env *e)
{
        static u_long next_env_id = 0;
        // lower bits of envid hold e's position in the envs array
        u_int idx = e - envs;
        
        printf("env.c:mkenvid:\txx:%x\n",(int)&idx);

        // high bits of envid hold an increasing number
        return(++next_env_id << (1 + LOG2NENV)) | idx;
}

//
// Converts an envid to an env pointer.
//
// RETURNS
//   env pointer -- on success and sets *error = 0
//   NULL -- on failure, and sets *error = the error number
//
int envid2env(u_int envid, struct Env **penv, int checkperm)
{
  
        struct Env *e;
        u_int cur_envid;
        if (envid == 0) {
                *penv = curenv;
                return 0;
        }

        e = &envs[ENVX(envid)];
        if (e->env_status == ENV_FREE || e->env_id != envid) {
                *penv = 0;
                return -E_BAD_ENV;
        }

        if (checkperm) {
                cur_envid = envid;
                while (&envs[ENVX(cur_envid)] != curenv && ENVX(cur_envid) != 0)
                {
                        envid = envs[ENVX(cur_envid)].env_parent_id;
                        cur_envid = envid;
                }
                if (ENVX(cur_envid) == 0)
                {
                        *penv = 0;
                        return -E_BAD_ENV;
                }
        }
        *penv = e;
        return 0;
        
}


//
// Marks all environments in 'envs' as free and inserts them into 
// the env_free_list.  Insert in reverse order, so that
// the first call to env_alloc() returns envs[0].
//
void
env_init(void)
{
        int i;
        LIST_INIT(&env_free_list);
        for (i = NENV - 1; i >= 0; i--)
        {
                envs[i].env_id = 0;
                envs[i].env_status = ENV_FREE;
                LIST_INSERT_HEAD(&env_free_list, &envs[i], env_link);
        }
	printf("env_init: success\n");
        return &envs[0];

}

//
// Initializes the kernel virtual memory layout for environment e.
//
// Allocates a page directory and initializes it.  Sets
// e->env_cr3 and e->env_pgdir accordingly.
//
// RETURNS
//   0 -- on sucess
//   <0 -- otherwise 
//
static int
env_setup_vm(struct Env *e)
{
         // Hint:

        int i, r;
        struct Page *p = NULL;

        Pde *pgdir;
        if ((r = page_alloc(&p)) < 0)
        {
                panic("env_setup_vm - page_alloc error\n");
                        return r;
        }
        p->pp_ref++;
        pgdir = (Pde *)page2kva(p);
//      printf("env.c:env_setup_vm:\tpgdir\t:con:%x\n",(int)pgdir);
        for(i=0;i<UTOP; i+=BY2PG)
                pgdir[PDX(i)] = 0;
        for(i=PDX(UTOP); i<1024;i++)
        {
//printf("boot_pgdir[%d] = %x\n",i,boot_pgdir[PDX(i)]);
                pgdir[i] = boot_pgdir[i];
        }
//printf("env_setup_vm :        2\n");

        e->env_pgdir = pgdir;
//printf("env_setup_vm :        3\n");
        // ...except at VPT and UVPT.  These map the env's own page table

        //e->env_pgdir[PDX(UVPT)]  = e->env_cr3 | PTE_P | PTE_U;

        e->env_cr3 = PADDR(pgdir);

        boot_map_segment(e->env_pgdir,UVPT,PDMAP,PADDR(pgdir),PTE_R);
//      printf("env.c:env_setup_vm:\tboot_map_segment(%x,%x,%x,%x,PTE_R)\n",e->env_pgdir,UVPT,PDMAP,PADDR(pgdir));
        e->env_pgdir[PDX(UVPT)]  = e->env_cr3 | PTE_V | PTE_R;
        
        // e->env_pgdir = (void *)page2kva(p);
        // e->env_cr3 = page2pa(p);


        // for (i = PDX(UTOP); i <= PDX(~0); i++)
        //         e->env_pgdir[i] = boot_pgdir[i];
        // e->env_pgdir[PDX(VPT)]   = e->env_cr3 ;
        // e->env_pgdir[PDX(UVPT)]  = e->env_cr3 ;

        return 0;
}

//
// Allocates and initializes a new env.
//
// RETURNS
//   0 -- on success, sets *new to point at the new env 
//   <0 -- on failure
//
int
env_alloc(struct Env **new, u_int parent_id)
{

        int r;
        struct Env *e;

        e = LIST_FIRST(&env_free_list);
        if(e==NULL)
                return -E_NO_FREE_ENV;

        r = env_setup_vm(e);
	printf("env_alloc: r = %d\n", r);
        if(r < 0)
                return r;

        e->env_id = mkenvid(e);


        // Set the basic status variables.
        e->env_parent_id = parent_id;
        e->env_status = ENV_RUNNABLE;
        e->env_tf.regs[29] = USTACKTOP;
	e->env_tf.pc=UTEXT+0x20;
	printf("env_alloc: e->env_tf.regs[29] = %d\n", e->env_tf.regs[29]);
        e->env_tf.cp0_status = 0x10001004;

        LIST_REMOVE(e, env_link);
        *new = e;

        printf("[%08x] new env %08x\n", curenv ? curenv->env_id : 0, e->env_id);

        return 0;
}

//
// Sets up the the initial stack and program binary for a user process.
//
// This function loads the complete binary image, including a.out header,
// into the environment's user memory starting at virtual address UTEXT,
// and maps one page for the program's initial stack
// at virtual address USTACKTOP - BY2PG.
// Since the a.out header from the binary is mapped at virtual address UTEXT,
// the actual program text starts at virtual address UTEXT+0x20.
//
// This function does not allocate or clear the bss of the loaded program,
// and all mappings are read/write including those of the text segment.
//
static void 
load_icode(struct Env *e, u_char *binary, u_int size) {
    int i;
    struct Page *p = NULL;
    u_char *cpyb;
    cpyb = binary;
    for(i=0; i<size; i=i+BY2PG)
    {
        if(page_alloc(&p)!=0)
            panic("load_icode: page alloc error");
        else if(page_insert(e->env_pgdir, p, UTEXT + i, PTE_R)!=0)
            panic("load_icode: page_insert error");
        else
            bcopy(cpyb+i,page2kva(p),BY2PG);
    }
    if(page_alloc(&p)!=0)
        panic("load_icode:page alloc error");
    else if(page_insert(e->env_pgdir, p, USTACKTOP - BY2PG,PTE_R)!=0)
        panic("load_icode:page_insert error");
    e->env_tf.pc=*((unsigned int*)binary +6);
    printf("load_icode success\n");
}


//
// Allocates a new env and loads the a.out binary into it.
//  - new env's parent env id is 0
void
env_create(u_char *binary, int size)
{
        struct Env *e;
        int r;

        r = env_alloc (&e, 0);
	printf("env_create: r = %d\n", r);
        if (r < 0)
        	panic ("env_create: %e", r);

        load_icode(e, binary, size);
	printf("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
}


//
// Frees env e and all memory it uses.
// 
void
env_free(struct Env *e)
{
        Pte *pt;
        u_int pdeno, pteno, pa;

        
        printf("[%08x] free env %08x\n", curenv ? curenv->env_id : 0, e->env_id);

        // Flush all pages

        for (pdeno = 0; pdeno < PDX(UTOP); pdeno++) {
                if (!(e->env_pgdir[pdeno] & PTE_V))
                        continue;
                pa = PTE_ADDR(e->env_pgdir[pdeno]);
                pt = (Pte*)KADDR(pa);
                for (pteno = 0; pteno <= PTX(~0); pteno++)
                        if (pt[pteno] & PTE_V)
                                page_remove(e->env_pgdir, (pdeno << PDSHIFT) | (pteno << PGSHIFT));

                e->env_pgdir[pdeno] = 0;

                page_decref(pa2page(pa));
        }
        pa = e->env_cr3;
        e->env_pgdir = 0;
        e->env_cr3 = 0;
        page_decref(pa2page(pa));



        e->env_status = ENV_FREE;
        LIST_INSERT_HEAD(&env_free_list, e, env_link);
}

// Frees env e.  And schedules a new env
// if e is the current env.
//
void
env_destroy(struct Env *e) 
{
        env_free(e);
        if (curenv == e) {
                curenv = NULL;
                bcopy((int)KERNEL_SP-sizeof(struct Trapframe),TIMESTACK-sizeof(struct Trapframe),sizeof(struct Trapframe));
                printf("i am killed ... \n");
                //sched_yield();
        }
}


extern void env_pop_tf(struct Trapframe *tf,int id);
extern void lcontext(u_int contxt);

void
env_run(struct Env *e)
{
	struct Trapframe*  old;
	old=(struct Trapframe*)(TIMESTACK-sizeof(struct Trapframe));
	if(curenv)
	{	
		bcopy(old,&curenv->env_tf,sizeof(struct Trapframe));
		curenv->env_tf.pc=old->cp0_epc;

	
	}
        if (curenv != e){
                curenv = e;
		printf("changing to ENV_ID:%x ...",(int)curenv->env_id);
                
        }
	lcontext(KADDR(curenv->env_cr3));
	//sched_yield();
        env_pop_tf(&curenv->env_tf,GET_ENV_ASID(curenv->env_id));
        panic("env_run failure");

}

